﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Text;
using Spring.Objects.Factory.Config;
using Strongshell.Recoil.Core.Composition.Parsing.DependencyInjection;

namespace Strongshell.Recoil.Core.Composition.Parsing.Handlers
{
    public class CallExpressionHandler : IExpressionHandler
    {
        private readonly IObjectConfigurationRegistry objectConfigurationRegistry;
        private readonly IExpressionParser expressionParser;
        private readonly IExpressionHandler compilingHandler;

        public CallExpressionHandler(IObjectConfigurationRegistry objectConfigurationRegistry, IExpressionParser expressionParser, IExpressionHandler compilingHandler)
        {
            this.objectConfigurationRegistry = objectConfigurationRegistry;
            this.expressionParser = expressionParser;
            this.compilingHandler = compilingHandler;
        }

        public void Handle(IDependencyInjector currentInjector, Expression currentExpression)
        {
            var callExpression = (MethodCallExpression)currentExpression;

            if (IsStubInvocation(callExpression))
            {
                if (callExpression.Arguments.Count == 1 && callExpression.Arguments[0] is ConstantExpression)
                {
                    var dependencyName = (string)((ConstantExpression)callExpression.Arguments[0]).Value;

                    currentInjector.Inject(new RuntimeObjectReference(dependencyName));
                }
                else
                {
                    currentInjector.Inject(
                        new RuntimeObjectReference(objectConfigurationRegistry.NameFactory.CreateName(callExpression.Type)));
                }
                return;
            }

            if (callExpression.Method.IsStatic && callExpression.Method.IsPublic)
            {
                var currentConfiguration = currentInjector.SelectNestableObjectConfiguration(callExpression.Method.ReturnType);

                currentConfiguration.Builder.ObjectDefinition.ObjectType = callExpression.Method.DeclaringType;
                currentConfiguration.Builder.SetFactoryMethod(callExpression.Method.Name);

                ParseFactoryArguments(callExpression, currentConfiguration);

                return;
            }

            var recievingObjectExpression = callExpression.Object as MethodCallExpression;

            if (recievingObjectExpression != null && IsStubInvocation(recievingObjectExpression))
            {
                var currentConfiguration = currentInjector.SelectNestableObjectConfiguration(callExpression.Method.ReturnType);

                currentConfiguration.Builder.ObjectDefinition.ObjectType = null;

                var factoryObjectName = recievingObjectExpression.Arguments.Count == 1 &&
                                        recievingObjectExpression.Arguments[0] is ConstantExpression
                                            ? (string)
                                              ((ConstantExpression) recievingObjectExpression.Arguments[0]).Value
                                            : objectConfigurationRegistry.NameFactory.CreateName(
                                                  recievingObjectExpression.Type);

                currentConfiguration.Builder.SetFactoryObject(factoryObjectName, callExpression.Method.Name);

                ParseFactoryArguments(callExpression, currentConfiguration);

                return;
            }

            compilingHandler.Handle(currentInjector, currentExpression);
        }

        private void ParseFactoryArguments(MethodCallExpression callExpression, ObjectConfiguration currentConfiguration)
        {
            foreach (var argument in callExpression.Arguments)
            {
                expressionParser.Parse(
                    new NestingInstantiationDependencyInjector(objectConfigurationRegistry,
                                                               currentConfiguration,
                                                               argument.Type),
                    argument);
            }
        }

        private static bool IsStubInvocation(MethodCallExpression callExpression)
        {
            return callExpression.Method.DeclaringType == typeof(WiringContainer)
                   && String.Equals(callExpression.Method.Name, "Wire", StringComparison.InvariantCultureIgnoreCase);
        }
    }
}
